BemÀstra React Testing Library (RTL) med denna kompletta guide. LÀr dig skriva effektiva, underhÄllbara och anvÀndarcentrerade tester för dina React-applikationer.
React Testing Library: En omfattande guide
I dagens snabbrörliga landskap för webbutveckling Àr det avgörande att sÀkerstÀlla kvaliteten och tillförlitligheten hos dina React-applikationer. React Testing Library (RTL) har vuxit fram som en populÀr och effektiv lösning för att skriva tester som fokuserar pÄ anvÀndarperspektivet. Denna guide ger en komplett översikt över RTL, och tÀcker allt frÄn grundlÀggande koncept till avancerade tekniker, för att ge dig kraften att bygga robusta och underhÄllbara React-applikationer.
Varför vÀlja React Testing Library?
Traditionella testmetoder förlitar sig ofta pÄ implementeringsdetaljer, vilket gör testerna sköra och benÀgna att gÄ sönder vid mindre kodÀndringar. RTL, Ä andra sidan, uppmuntrar dig att testa dina komponenter som en anvÀndare skulle interagera med dem, med fokus pÄ vad anvÀndaren ser och upplever. Detta tillvÀgagÄngssÀtt erbjuder flera viktiga fördelar:
- AnvÀndarcentrerad testning: RTL frÀmjar skrivandet av tester som Äterspeglar anvÀndarens perspektiv, vilket sÀkerstÀller att din applikation fungerar som förvÀntat ur slutanvÀndarens synvinkel.
- Minskad testskörhet: Genom att undvika att testa implementeringsdetaljer Àr det mindre troligt att RTL-tester gÄr sönder nÀr du refaktorerar din kod, vilket leder till mer underhÄllbara och robusta tester.
- FörbÀttrad koddesign: RTL uppmuntrar dig att skriva komponenter som Àr tillgÀngliga och lÀtta att anvÀnda, vilket leder till en bÀttre övergripande koddesign.
- Fokus pÄ tillgÀnglighet: RTL gör det enklare att testa tillgÀngligheten hos dina komponenter, vilket sÀkerstÀller att din applikation kan anvÀndas av alla.
- Förenklad testprocess: RTL tillhandahÄller ett enkelt och intuitivt API, vilket gör det lÀttare att skriva och underhÄlla tester.
Konfigurera din testmiljö
Innan du kan börja anvÀnda RTL mÄste du konfigurera din testmiljö. Detta innebÀr vanligtvis att installera nödvÀndiga beroenden och konfigurera ditt testramverk.
FörutsÀttningar
- Node.js och npm (eller yarn): Se till att du har Node.js och npm (eller yarn) installerat pÄ ditt system. Du kan ladda ner dem frÄn den officiella Node.js-webbplatsen.
- React-projekt: Du bör ha ett befintligt React-projekt eller skapa ett nytt med Create React App eller ett liknande verktyg.
Installation
Installera följande paket med npm eller yarn:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Eller, med yarn:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Förklaring av paket:
- @testing-library/react: KÀrnbiblioteket för att testa React-komponenter.
- @testing-library/jest-dom: TillhandahÄller anpassade Jest-matchers för att göra assertions om DOM-noder.
- Jest: Ett populÀrt JavaScript-testramverk.
- babel-jest: En Jest-transformer som anvÀnder Babel för att kompilera din kod.
- @babel/preset-env: En Babel-preset som bestÀmmer vilka Babel-plugins och presets som behövs för att stödja dina mÄlmiljöer.
- @babel/preset-react: En Babel-preset för React.
Konfiguration
Skapa en fil med namnet `babel.config.js` i roten av ditt projekt med följande innehÄll:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Uppdatera din `package.json`-fil för att inkludera ett testskript:
{
"scripts": {
"test": "jest"
}
}
Skapa en `jest.config.js`-fil i roten av ditt projekt för att konfigurera Jest. En minimal konfiguration kan se ut sÄ hÀr:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['/src/setupTests.js'],
};
Skapa en `src/setupTests.js`-fil med följande innehÄll. Detta sÀkerstÀller att Jest DOM-matchers Àr tillgÀngliga i alla dina tester:
import '@testing-library/jest-dom/extend-expect';
Skriva ditt första test
LÄt oss börja med ett enkelt exempel. Anta att du har en React-komponent som visar ett hÀlsningsmeddelande:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
LÄt oss nu skriva ett test för den hÀr komponenten:
// src/components/Greeting.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('renders a greeting message', () => {
render(<Greeting name="World" />);
const greetingElement = screen.getByText(/Hello, World!/i);
expect(greetingElement).toBeInTheDocument();
});
Förklaring:
- `render`: Denna funktion renderar komponenten i DOM.
- `screen`: Detta objekt tillhandahÄller metoder för att söka i DOM.
- `getByText`: Denna metod hittar ett element via dess textinnehÄll. Flaggan `/i` gör sökningen skiftlÀgesokÀnslig.
- `expect`: Denna funktion anvÀnds för att göra assertions om komponentens beteende.
- `toBeInTheDocument`: Denna matcher försÀkrar att elementet finns i DOM.
För att köra testet, kör följande kommando i din terminal:
npm test
Om allt Àr korrekt konfigurerat bör testet passera.
Vanliga RTL-sökfrÄgor
RTL tillhandahÄller olika sökmetoder för att hitta element i DOM. Dessa sökfrÄgor Àr utformade för att efterlikna hur anvÀndare interagerar med din applikation.
`getByRole`
Denna sökfrÄga hittar ett element baserat pÄ dess ARIA-roll. Det Àr god praxis att anvÀnda `getByRole` nÀr det Àr möjligt, eftersom det frÀmjar tillgÀnglighet och sÀkerstÀller att dina tester Àr motstÄndskraftiga mot Àndringar i den underliggande DOM-strukturen.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Denna sökfrÄga hittar ett element via texten i dess tillhörande etikett. Det Àr anvÀndbart för att testa formulÀrelement.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Denna sökfrÄga hittar ett element via dess platshÄllartext.
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Denna sökfrÄga hittar ett bildelement via dess alt-text. Det Àr viktigt att tillhandahÄlla meningsfull alt-text för alla bilder för att sÀkerstÀlla tillgÀnglighet.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Denna sökfrÄga hittar ett element via dess title-attribut.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Denna sökfrÄga hittar ett element via dess visningsvÀrde. Detta Àr anvÀndbart för att testa formulÀrfÀlt med förifyllda vÀrden.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
`getAllBy*`-sökfrÄgor
Utöver `getBy*`-sökfrÄgorna tillhandahÄller RTL ocksÄ `getAllBy*`-sökfrÄgor, som returnerar en array av matchande element. Dessa Àr anvÀndbara nÀr du behöver försÀkra dig om att flera element med samma egenskaper finns i DOM.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
`queryBy*`-sökfrÄgor
`queryBy*`-sökfrÄgorna liknar `getBy*`-sökfrÄgorna, men de returnerar `null` om inget matchande element hittas, istÀllet för att kasta ett fel. Detta Àr anvÀndbart nÀr du vill försÀkra dig om att ett element *inte* finns i DOM.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
`findBy*`-sökfrÄgor
`findBy*`-sökfrÄgorna Àr asynkrona versioner av `getBy*`-sökfrÄgorna. De returnerar ett Promise som uppfylls nÀr det matchande elementet hittas. Dessa Àr anvÀndbara för att testa asynkrona operationer, som att hÀmta data frÄn ett API.
// Simulating an asynchronous data fetch
const fetchData = () => new Promise(resolve => {
setTimeout(() => resolve('Data Loaded!'), 1000);
});
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
test('loads data asynchronously', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText('Data Loaded!');
expect(dataElement).toBeInTheDocument();
});
Simulera anvÀndarinteraktioner
RTL tillhandahÄller API:erna `fireEvent` och `userEvent` för att simulera anvÀndarinteraktioner, som att klicka pÄ knappar, skriva i inmatningsfÀlt och skicka formulÀr.
`fireEvent`
`fireEvent` lÄter dig programmatiskt utlösa DOM-hÀndelser. Det Àr ett lÀgre nivÄ-API som ger dig finkornig kontroll över de hÀndelser som utlöses.
<button onClick={() => alert('Button clicked!')}>Click me</button>
import { fireEvent } from '@testing-library/react';
test('simulates a button click', () => {
const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {});
render(<button onClick={() => alert('Button clicked!')}>Click me</button>);
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(alertMock).toHaveBeenCalledTimes(1);
alertMock.mockRestore();
});
`userEvent`
`userEvent` Àr ett högre nivÄ-API som simulerar anvÀndarinteraktioner mer realistiskt. Det hanterar detaljer som fokushantering och hÀndelseordning, vilket gör dina tester mer robusta och mindre sköra.
<input type="text" onChange={e => console.log(e.target.value)} />
import userEvent from '@testing-library/user-event';
test('simulates typing in an input field', () => {
const inputElement = screen.getByRole('textbox');
userEvent.type(inputElement, 'Hello, world!');
expect(inputElement).toHaveValue('Hello, world!');
});
Testa asynkron kod
MÄnga React-applikationer involverar asynkrona operationer, som att hÀmta data frÄn ett API. RTL tillhandahÄller flera verktyg för att testa asynkron kod.
`waitFor`
`waitFor` lÄter dig vÀnta pÄ att ett villkor ska bli sant innan du gör en assertion. Det Àr anvÀndbart för att testa asynkrona operationer som tar lite tid att slutföra.
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
setTimeout(() => {
setData('Data loaded!');
}, 1000);
}, []);
return <div>{data}</div>;
}
import { waitFor } from '@testing-library/react';
test('waits for data to load', async () => {
render(<MyComponent />);
await waitFor(() => screen.getByText('Data loaded!'));
const dataElement = screen.getByText('Data loaded!');
expect(dataElement).toBeInTheDocument();
});
`findBy*`-sökfrÄgor
Som nÀmnts tidigare Àr `findBy*`-sökfrÄgorna asynkrona och returnerar ett Promise som uppfylls nÀr det matchande elementet hittas. Dessa Àr anvÀndbara för att testa asynkrona operationer som resulterar i Àndringar i DOM.
Testa Hooks
React Hooks Àr ÄteranvÀndbara funktioner som kapslar in stateful logik. RTL tillhandahÄller verktyget `renderHook` frÄn `@testing-library/react-hooks` (som Àr förÄldrat till förmÄn för `@testing-library/react` direkt frÄn och med v17) för att testa anpassade Hooks isolerat.
// src/hooks/useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => prevCount - 1);
};
return { count, increment, decrement };
}
export default useCounter;
// src/hooks/useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
test('increments the counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Förklaring:
- `renderHook`: Denna funktion renderar Hooken och returnerar ett objekt som innehÄller resultatet av Hooken.
- `act`: Denna funktion anvÀnds för att omsluta all kod som orsakar tillstÄndsuppdateringar. Detta sÀkerstÀller att React kan bunta ihop och bearbeta uppdateringarna korrekt.
Avancerade testtekniker
NÀr du har bemÀstrat grunderna i RTL kan du utforska mer avancerade testtekniker för att förbÀttra kvaliteten och underhÄllbarheten pÄ dina tester.
Mocka moduler
Ibland kan du behöva mocka externa moduler eller beroenden för att isolera dina komponenter och kontrollera deras beteende under testning. Jest tillhandahÄller ett kraftfullt mocknings-API för detta ÀndamÄl.
// src/api/dataService.js
export const fetchData = async () => {
const response = await fetch('/api/data');
const data = await response.json();
return data;
};
// src/components/MyComponent.js
import React, { useState, useEffect } from 'react';
import { fetchData } from '../api/dataService';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
// src/components/MyComponent.test.js
import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
import * as dataService from '../api/dataService';
jest.mock('../api/dataService');
test('fetches data from the API', async () => {
dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' });
render(<MyComponent />);
await waitFor(() => screen.getByText('Mocked data!'));
expect(screen.getByText('Mocked data!')).toBeInTheDocument();
expect(dataService.fetchData).toHaveBeenCalledTimes(1);
});
Förklaring:
- `jest.mock('../api/dataService')`: Denna rad mockar `dataService`-modulen.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Denna rad konfigurerar den mockade `fetchData`-funktionen att returnera ett Promise som uppfylls med den specificerade datan.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Denna rad försÀkrar att den mockade `fetchData`-funktionen anropades en gÄng.
Context Providers
Om din komponent Àr beroende av en Context Provider, mÄste du omsluta din komponent i providern under testning. Detta sÀkerstÀller att komponenten har tillgÄng till kontextvÀrdena.
// src/contexts/ThemeContext.js
import React, { createContext, useState } from 'react';
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// src/components/MyComponent.js
import React, { useContext } from 'react';
import { ThemeContext } from '../contexts/ThemeContext';
function MyComponent() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ backgroundColor: theme === 'light' ? '#fff' : '#000', color: theme === 'light' ? '#000' : '#fff' }}>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
// src/components/MyComponent.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
import { ThemeProvider } from '../contexts/ThemeContext';
test('toggles the theme', () => {
render(
<ThemeProvider>
<MyComponent />
</ThemeProvider>
);
const themeParagraph = screen.getByText(/Current theme: light/i);
const toggleButton = screen.getByRole('button', { name: /Toggle Theme/i });
expect(themeParagraph).toBeInTheDocument();
fireEvent.click(toggleButton);
expect(screen.getByText(/Current theme: dark/i)).toBeInTheDocument();
});
Förklaring:
- Vi omsluter `MyComponent` i `ThemeProvider` för att tillhandahÄlla den nödvÀndiga kontexten under testning.
Testa med Router
NÀr du testar komponenter som anvÀnder React Router, mÄste du tillhandahÄlla en mockad Router-kontext. Du kan uppnÄ detta genom att anvÀnda `MemoryRouter`-komponenten frÄn `react-router-dom`.
// src/components/MyComponent.js
import React from 'react';
import { Link } from 'react-router-dom';
function MyComponent() {
return (
<div>
<Link to="/about">Go to About Page</Link>
</div>
);
}
// src/components/MyComponent.test.js
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
test('renders a link to the about page', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
const linkElement = screen.getByRole('link', { name: /Go to About Page/i });
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', '/about');
});
Förklaring:
- Vi omsluter `MyComponent` i `MemoryRouter` för att tillhandahÄlla en mockad Router-kontext.
- Vi försÀkrar att lÀnk-elementet har det korrekta `href`-attributet.
BÀsta praxis för att skriva effektiva tester
HÀr Àr nÄgra bÀsta praxis att följa nÀr du skriver tester med RTL:
- Fokusera pÄ anvÀndarinteraktioner: Skriv tester som simulerar hur anvÀndare interagerar med din applikation.
- Undvik att testa implementeringsdetaljer: Testa inte den interna funktionen hos dina komponenter. Fokusera istÀllet pÄ det observerbara beteendet.
- Skriv tydliga och koncisa tester: Gör dina tester lÀtta att förstÄ och underhÄlla.
- AnvÀnd meningsfulla testnamn: VÀlj testnamn som korrekt beskriver det beteende som testas.
- HÄll tester isolerade: Undvik beroenden mellan tester. Varje test bör vara oberoende och fristÄende.
- Testa kantfall: Testa inte bara den glada vÀgen. Se till att Àven testa kantfall och feltillstÄnd.
- Skriv tester innan du kodar: ĂvervĂ€g att anvĂ€nda testdriven utveckling (TDD) för att skriva tester innan du skriver din kod.
- Följ "AAA"-mönstret: Arrange, Act, Assert. Detta mönster hjÀlper till att strukturera dina tester och göra dem mer lÀsbara.
- HÄll dina tester snabba: LÄngsamma tester kan avskrÀcka utvecklare frÄn att köra dem ofta. Optimera dina tester för hastighet genom att mocka nÀtverksanrop och minimera mÀngden DOM-manipulation.
- AnvÀnd beskrivande felmeddelanden: NÀr assertions misslyckas bör felmeddelandena ge tillrÀckligt med information för att snabbt identifiera orsaken till felet.
Slutsats
React Testing Library Àr ett kraftfullt verktyg för att skriva effektiva, underhÄllbara och anvÀndarcentrerade tester för dina React-applikationer. Genom att följa principerna och teknikerna som beskrivs i denna guide kan du bygga robusta och pÄlitliga applikationer som möter dina anvÀndares behov. Kom ihÄg att fokusera pÄ att testa frÄn anvÀndarens perspektiv, undvika att testa implementeringsdetaljer och skriva tydliga och koncisa tester. Genom att anamma RTL och tillÀmpa bÀsta praxis kan du avsevÀrt förbÀttra kvaliteten och underhÄllbarheten i dina React-projekt, oavsett din plats eller de specifika kraven frÄn din globala publik.